package com.noahsloan.nutils.collections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

/**
 * Set, ordered by insertion order. Refuses to add new elements if they are
 * already in the SetList.
 * 
 * @author noah
 * 
 * @param <E>
 */
public class SetList<E> extends ListWrapper<E> implements Set<E> {

	private Set<E> set;

	public SetList() {
		this(new ArrayList<E>());
	}

	/**
	 * The list to be backed by.
	 * 
	 * @param list
	 */
	public SetList(List<E> list) {
		super(list);
		this.set = new HashSet<E>();
	}

	@Override
	public boolean add(E o) {
		if (set.contains(o)) {
			return false;
		}
		set.add(o);
		return super.add(o);
	}

	@Override
	public void add(int index, E element) {
		if (set.contains(element)) {
			return;
		}
		set.add(element);
		super.add(index, element);
	}

	@Override
	public boolean addAll(Collection<? extends E> c) {
		boolean result = false;
		for (E e : c) {
			if (add(e)) {
				result = true;
			}
		}
		return result;
	}

	@Override
	public boolean addAll(int index, Collection<? extends E> c) {
		boolean result = !set.containsAll(c);
		for (E e : c) {
			add(index++, e);
		}
		return result;
	}

	@Override
	public E remove(int index) {
		E e = super.remove(index);
		set.remove(e);
		return e;
	}

	@Override
	public boolean remove(Object o) {
		set.remove(o);
		return super.remove(o);
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		set.removeAll(c);
		return super.removeAll(c);
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		set.retainAll(c);
		return super.retainAll(c);
	}

	@Override
	public void clear() {
		set.clear();
		super.clear();
	}

	@Override
	public boolean contains(Object o) {
		return set.contains(o);
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		return set.containsAll(c);
	}

	@Override
	public Iterator<E> iterator() {
		return listIterator();
	}

	@Override
	public ListIterator<E> listIterator() {
		return listIterator(0);
	}

	@Override
	public ListIterator<E> listIterator(int index) {
		return new SetListIterator(index);
	}

	private class SetListIterator implements ListIterator<E> {

		private ListIterator<E> it;

		private E last;

		public SetListIterator(int index) {
			super();
			this.it = SetList.this.listIterator(index);
		}

		public void add(E o) {
			if (!set.contains(o)) {
				it.add(o);
				set.add(o);
			}
		}

		public boolean hasNext() {
			return it.hasNext();
		}

		public boolean hasPrevious() {
			return it.hasPrevious();
		}

		public E next() {
			return last = it.next();
		}

		public int nextIndex() {
			return it.nextIndex();
		}

		public E previous() {
			return last = it.previous();
		}

		public int previousIndex() {
			return it.previousIndex();
		}

		public void remove() {
			it.remove();
			set.remove(last);
		}

		/**
		 * Replaces the element last returned by next or previous if and only
		 * the set not containing the last element does not contain the given
		 * element.
		 */
		public void set(E o) {
			set.remove(last);
			if (!set.contains(o)) {
				try {
					it.set(o);
					set.add(o);
				} catch (RuntimeException e) {
					set.add(last);
					throw e;
				}
			} else {
				set.add(last);
			}
		}

	}

}
